﻿// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using Nova.Parsing;
using Nova.Rendering;

namespace Nova.CodeDOM
{
    /// <summary>
    /// The common base class of <see cref="If"/> and <see cref="ElseIf"/>.
    /// </summary>
    /// <remarks>
    /// An <see cref="If"/> or <see cref="ElseIf"/> statement represents conditional flow control, and includes a conditional
    /// expression, a body, and an optional <see cref="Else"/> or <see cref="ElseIf"/> statement.
    /// </remarks>
    public class IfBase : BlockStatement
    {
        #region /* FIELDS */

        protected Expression _conditional;
        protected BlockStatement _elsePart;  // Must be an Else or an ElseIf

        #endregion

        #region /* CONSTRUCTORS */

        protected IfBase(Expression conditional, CodeObject body)
            : base(body, false)  // Don't allow null body for If/ElseIf
        {
            Conditional = conditional;
        }

        protected IfBase(Expression conditional, CodeObject body, Else @else)
            : this(conditional, body)
        {
            ElsePart = @else;
        }

        protected IfBase(Expression conditional, CodeObject body, ElseIf elseIf)
            : this(conditional, body)
        {
            ElsePart = elseIf;
        }

        protected IfBase(Expression conditional)
            : this(conditional, (CodeObject)null)
        { }

        protected IfBase(Expression conditional, Else @else)
            : this(conditional, null, @else)
        { }

        protected IfBase(Expression conditional, ElseIf elseIf)
            : this(conditional, null, elseIf)
        { }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The conditional <see cref="Expression"/>.
        /// </summary>
        public Expression Conditional
        {
            get { return _conditional; }
            set { SetField(ref _conditional, value, true); }
        }

        /// <summary>
        /// The optional <see cref="ElseIf"/> or <see cref="Else"/> part.
        /// </summary>
        public BlockStatement ElsePart
        {
            get { return _elsePart; }
            set { SetField(ref _elsePart, value, false); }
        }

        /// <summary>
        /// True if there is an <see cref="ElseIf"/> or <see cref="Else"/> part.
        /// </summary>
        public bool HasElse
        {
            get { return (_elsePart != null); }
        }

        /// <summary>
        /// True for multi-part statements, such as try/catch/finally or if/else.
        /// </summary>
        public override bool IsMultiPart
        {
            get { return HasElse; }
        }

        #endregion

        #region /* METHODS */

        protected override bool IsChildIndented(CodeObject obj)
        {
            // The child object can only be indented if it's the first thing on the line
            if (obj.IsFirstOnLine)
            {
                // If the child isn't an else part and isn't a prefix, it should be indented
                // (we can't compare to _elsePart because it won't be set yet if we're still
                // parsing the child, so just check for Else and ElseIf objects instead).
                return (!(obj is Else || obj is ElseIf) && !IsChildPrefix(obj));
            }
            return false;
        }

        /// <summary>
        /// Deep-clone the code object.
        /// </summary>
        public override CodeObject Clone()
        {
            IfBase clone = (IfBase)base.Clone();
            clone.CloneField(ref clone._conditional, _conditional);
            clone.CloneField(ref clone._elsePart, _elsePart);
            return clone;
        }

        #endregion

        #region /* PARSING */

        protected IfBase(Parser parser, CodeObject parent)
            : base(parser, parent)
        { }

        protected void ParseIf(Parser parser, CodeObject parent)
        {
            // Flush any unused objects first, so that they don't interfere with skipping
            // compiler directives below, or the parsing of any 'else' part.
            if (parser.HasUnused && _parent is BlockStatement)
                ((BlockStatement)_parent).Body.FlushUnused(parser);

            ParseKeywordArgumentBody(parser, ref _conditional, false, false);  // Parse keyword, argument, and body

            // Skip over any compiler directives that might occur before an 'else', adding them to the unused list
            ParseAnnotations(parser, parent, false, true);

            // Parse optional 'else' or 'else if' child part
            if (parser.TokenText == Else.ParseToken)
            {
                _elsePart = ElseIf.Parse(parser, this);
                if (_elsePart == null)
                    _elsePart = Else.Parse(parser, this);
            }
            else
            {
                // If there wasn't any 'else', and we skipped any compiler directives, we have to move
                // them now so they can be manually flushed by the parent Block *after* this statement.
                if (parser.HasUnused)
                    parser.MoveUnusedToPostUnused();
            }
        }

        #endregion

        #region /* FORMATTING */

        /// <summary>
        /// True if the <see cref="Statement"/> has an argument.
        /// </summary>
        public override bool HasArgument
        {
            get { return true; }
        }

        /// <summary>
        /// True if the <see cref="BlockStatement"/> always requires braces.
        /// </summary>
        public override bool HasBracesAlways
        {
            get { return false; }
        }

        /// <summary>
        /// Determines if the code object only requires a single line for display.
        /// </summary>
        public override bool IsSingleLine
        {
            get
            {
                return (base.IsSingleLine && (_conditional == null || (!_conditional.IsFirstOnLine && _conditional.IsSingleLine))
                    && (_elsePart == null || (!_elsePart.IsFirstOnLine && _elsePart.IsSingleLine)));
            }
            set
            {
                base.IsSingleLine = value;
                if (_conditional != null)
                {
                    if (value)
                        _conditional.IsFirstOnLine = false;
                    _conditional.IsSingleLine = value;
                }
                if (_elsePart != null)
                {
                    _elsePart.IsFirstOnLine = !value;
                    _elsePart.IsSingleLine = value;
                }
            }
        }

        #endregion

        #region /* RENDERING */

        protected override void AsTextArgument(CodeWriter writer, RenderFlags flags)
        {
            if (_conditional != null)
            {
                // Technically, we should indent all expressions relative to their starting position.  However, this
                // would be a big deviation from standard formatting, and so would have to be optional.  We'd have to
                // wrap all statements with expressions like this:
                //writer.BeginIndentOnNewLineRelativeToCurrentPosition(this);
                _conditional.AsText(writer, flags);
                //writer.EndIndentation(this);
            }
        }

        protected override void AsTextAfter(CodeWriter writer, RenderFlags flags)
        {
            base.AsTextAfter(writer, flags);
            if (HasElse && !flags.HasFlag(RenderFlags.Description))
                _elsePart.AsText(writer, flags | RenderFlags.PrefixSpace);
        }

        #endregion
    }
}
